package org.youyun56.gpsserver;

import java.io.File;
import java.io.FileInputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.sql.DataSource;

import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.Status;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.SearchAttribute;
import net.sf.ehcache.config.Searchable;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.Direction;
import net.sf.ehcache.search.Query;
import net.sf.ehcache.search.Result;
import net.sf.ehcache.search.Results;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy.MemoryStoreEvictionPolicyEnum;

import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.core.session.IoSession;
import org.youyun56.gpsserver.common.GoodSearch;
import org.youyun56.gpsserver.common.Page;
import org.youyun56.gpsserver.common.PathConfig;
import org.youyun56.gpsserver.common.SessionManager;
import org.youyun56.gpsserver.db.CityDao;
import org.youyun56.gpsserver.db.GoodDao;
import org.youyun56.gpsserver.entity.Car;
import org.youyun56.gpsserver.entity.City;
import org.youyun56.gpsserver.entity.Good;
import org.youyun56.gpsserver.entity.Im;

import com.j3.db.ConnectionManager;

public enum CacheFactory {

    instance;
    private static CacheManager cacheManager;
    public static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
    private static ScheduledFuture<?> scheduledFuture;
    private final static Log log = LogFactory.getLog(CacheFactory.class);
    private int maxSaveTime = 0;
    public static GoodDao goodDao = new GoodDao();
    public static CityDao cityDao = new CityDao();
    private Map<Integer, AtomicLong> increments = new HashMap<Integer, AtomicLong>();
    public static Map<Integer, AtomicLong> savePoint = new HashMap<Integer, AtomicLong>();
    private final static long saveCacheRate = 10;
    private final static String FROM_CITY = "FROMCITY";
    private final static String TO_CITY = "TOCITY";
    private final static String ADD_TIME_KEY = "addTime";
    private final static String HearyType = "heavyType";
    private final static String Hearvy = "hearvy";
    private final static String HearvyUnin = "hearvyUnin";
    private final static String Mi = "mi";
    private final static String CarType = "carType";
    public final static ConcurrentHashMap<Integer, ConcurrentHashMap<String, ConcurrentLinkedQueue<Im>>> ims = new ConcurrentHashMap<Integer, ConcurrentHashMap<String, ConcurrentLinkedQueue<Im>>>();
    public final static Properties props = new Properties();
    static {
        try {
            Configuration config = new Configuration();
            CacheConfiguration cacheConfig = new CacheConfiguration();
            cacheConfig.setLogging(true);
            cacheConfig.setMaxElementsInMemory(20000);
            cacheConfig.setEternal(false);
            cacheConfig.setTimeToIdleSeconds(3600);
            cacheConfig.setTimeToLiveSeconds(3600);
            cacheConfig.setOverflowToDisk(false);
            cacheConfig.setDiskPersistent(false);
            cacheConfig.setStatistics(true);
            Searchable searchable = new Searchable();
            searchable.addSearchAttribute(new SearchAttribute().name(ADD_TIME_KEY).expression("value.getAddTime()"));
            searchable.addSearchAttribute(new SearchAttribute().name(FROM_CITY).expression("value.getFromCity()"));
            searchable.addSearchAttribute(new SearchAttribute().name(TO_CITY).expression("value.getToCity()"));
            searchable.addSearchAttribute(new SearchAttribute().name(Hearvy).expression("value.getHearvy()"));
            searchable.addSearchAttribute(new SearchAttribute().name(HearvyUnin).expression("value.getHearvyUnin()"));
            searchable.addSearchAttribute(new SearchAttribute().name(HearyType).expression("value.getHeavyType()"));
            searchable.addSearchAttribute(new SearchAttribute().name(Mi).expression("value.getMi()"));
            searchable.addSearchAttribute(new SearchAttribute().name(CarType).expression("value.getCarType()"));
            cacheConfig.searchable(searchable);
            cacheConfig.setDiskExpiryThreadIntervalSeconds(600);
            cacheConfig.setMemoryStoreEvictionPolicy(MemoryStoreEvictionPolicyEnum.FIFO.name());
            config.setDefaultCacheConfiguration(cacheConfig);
            // DiskStoreConfiguration diskStoreConfiguration = new
            // DiskStoreConfiguration();
            // diskStoreConfiguration.setPath(System.getProperty("java.io.tmpdir",
            // System.getProperty("user.dir")));
            // config.diskStore(diskStoreConfiguration);
            cacheManager = new CacheManager(config);
            cacheManager.setCacheManagerEventListener(new CacheManagerEventListener());
            // cacheManager.addCache(IM);
            // cacheManager.addCache(GOODS);
            // cacheManager.getEhcache(IM);
            initDB();
            CacheFactory.instance.init(cityDao.findAll());
            scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
            scheduledFuture = scheduledThreadPoolExecutor.scheduleWithFixedDelay(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        CacheFactory.instance.saveCache();
                    }
                    catch (Exception e) {
                        log.error(e);
                    }

                }
            }, saveCacheRate, saveCacheRate, TimeUnit.SECONDS);
        }
        catch (Exception e) {
            // TODO: handle exception
            log.error("init failure shutdown system", e);
            System.exit(0);
        }

    }

    public static void start() {
        log.info("started cache!");
    }

    private static void initDB() throws Exception {
        props.load(new FileInputStream(PathConfig.ROOT + File.separator + "config.properties"));
        // RSAManager manager = new RSAManager(PathConfig.ROOT + File.separator
        // + "oid");
        // props.put("password",
        // manager.decrypt(props.getProperty("password")));
        DataSource bds = BasicDataSourceFactory.createDataSource(props);
        ConnectionManager.registerDataSource("gps", bds);
        ConnectionManager.setDefaultDataSourceName("gps");
    }

    private void init(List<City> citys) throws SQLException {
        for (City city : citys) {
            int index = goodDao.getMaxIndex(city.getId());
            increments.put(city.getId(), new AtomicLong(index));
            savePoint.put(city.getId(), new AtomicLong(index));
            ims.put(city.getId().intValue(), new ConcurrentHashMap<String, ConcurrentLinkedQueue<Im>>());
            SessionManager.pool.put(city.getId().intValue(), new ConcurrentHashMap<String, IoSession>());
            cacheManager.addCache(String.valueOf(city.getId()));
            List<Good> goods = goodDao.reverseGood(city.getId());
            Ehcache goodCache = cacheManager.getCache(String.valueOf(city.getId()));
            for (Good good : goods) {
                goodCache.put(new Element(good.getIndex(), good));
            }
            log.info(String.format("cache %s has %d elements", String.valueOf(city.getId()), goodCache.getKeys().size()));
        }
    }

    public void addImCache(Integer cityId, String mobile, Im im) {
        ConcurrentHashMap<String, ConcurrentLinkedQueue<Im>> mobileIms = ims.get(cityId);
        if (mobileIms.get(mobile) == null) {
            mobileIms.putIfAbsent(mobile, new ConcurrentLinkedQueue<Im>());
        }
        mobileIms.get(mobile).add(im);
    }

    /**
     * @param cityId
     *            targetCity
     * @param good
     */
    public void addGoodCache(Integer cityId, Good good) {
        Ehcache cache = cacheManager.getEhcache(String.valueOf(cityId));
        long index = increments.get(cityId).incrementAndGet();
        good.setIndex(index);
        cache.put(new Element(index, good));
    }

    public List<Result> getGoodCache(Integer cityId, Page page) {
        if (cityId == null) {
            return null;
        }
        Ehcache cache = cacheManager.getEhcache(String.valueOf(cityId));

        Results rs = cache.createQuery().includeValues().addOrderBy(Query.KEY, Direction.DESCENDING).execute();
        page.setTotalCount(rs.size());
        return rs.range(page.getFirstResult(), page.getPageSize());

    }

    public List<Result> getGoodCache(int cityId, long index) {
        Ehcache cache = cacheManager.getEhcache(String.valueOf(cityId));
        Results rs = cache.createQuery().includeValues().addCriteria(Query.KEY.gt(index)).addOrderBy(Query.KEY, Direction.ASCENDING).execute();
        return rs.all();

    }

    @SuppressWarnings("unchecked")
    private void saveCache() {
        // log.info("save cached Goods");
        for (Integer cityId : savePoint.keySet()) {
            try {
                if (cacheManager.getStatus() == Status.STATUS_SHUTDOWN) {
                    break;
                }
                Ehcache ehcache = cacheManager.getEhcache(String.valueOf(cityId));
                AtomicLong index = savePoint.get(cityId);
                Query query = ehcache.createQuery().includeValues().addCriteria(Query.KEY.gt(index.get()));
                Results rss = query.execute();
                if (rss.size() == 0) {
                    continue;
                }
                Page page = new Page(1, 20);
                page.setTotalCount(rss.size());
                List<Good> goods = new ArrayList<Good>();
                for (int pageIndex = 1; pageIndex <= page.getPageCount(); pageIndex++) {
                    page.setPageIndex(pageIndex);
                    List<Result> list = rss.range(page.getFirstResult(), page.getPageSize());
                    for (Result rs : list) {
                        Good good = (Good) rs.getValue();
                        goods.add(good);
                        if (index.get() < good.getIndex()) {
                            index.getAndSet(good.getIndex());
                        }
                    }
                    goodDao.saveGood(goods);
                    goods.clear();
                }
            }
            catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }

    }

    public void shutdown() {
        try {
            scheduledFuture.cancel(true);
            scheduledThreadPoolExecutor.shutdownNow();
            saveCache();
            cacheManager.shutdown();
            log.info("cache ShutDown");
        }
        catch (Exception e) {
            // TODO: handle exception
            log.error("shutdown exception", e);
        }

    }

    public ConcurrentLinkedQueue<Im> getCacheIms(Car car) {
        return ims.get(car.getCityId()).remove(car.getCarMobile());
    }

    @SuppressWarnings("unchecked")
    public Object getAppendGood(long city, long index) {
        Ehcache cache = cacheManager.getEhcache(String.valueOf(city));
        Results rs = cache.createQuery().includeValues().addCriteria(Query.KEY.gt(index)).addOrderBy(Query.KEY, Direction.DESCENDING).execute();
        return rs.all();
    }

    @SuppressWarnings("unchecked")
    public Object getAppendGoodEnd(long city, long endIndex) {
        Ehcache cache = cacheManager.getEhcache(String.valueOf(city));
        Results rs = cache.createQuery().includeValues().addCriteria(Query.KEY.lt(endIndex)).maxResults(10)
                .addOrderBy(Query.KEY, Direction.DESCENDING).execute();
        return rs.all();
    }

    @SuppressWarnings({ "static-access", "unchecked" })
    public Object goodSearch(GoodSearch goodSearch) {
        Ehcache cache = cacheManager.getCache(String.valueOf(goodSearch.getTargetCity()));
        Attribute<Integer> fromCity = cache.getSearchAttribute(FROM_CITY);
        Attribute<Integer> toCity = cache.getSearchAttribute(TO_CITY);
        Attribute<String> hearvyType = cache.getSearchAttribute(HearyType);
        Attribute<Float> hearvy = cache.getSearchAttribute(Hearvy);
        Attribute<String> hearvyUnin = cache.getSearchAttribute(HearvyUnin);
        Attribute<Float> mi = cache.getSearchAttribute(Mi);
        Attribute<String> carType = cache.getSearchAttribute(CarType);
        Query query = cache.createQuery();
        if (goodSearch.getFromCity() != -1) {
            query.addCriteria(fromCity.eq(goodSearch.getFromCity()));
        }
        if (goodSearch.getToCity() != -1) {
            query.addCriteria(toCity.eq(goodSearch.getToCity()));
        }
        if (goodSearch.getGsHearvyType() != null) {
            query.addCriteria(hearvyType.eq(goodSearch.getGsHearvyType()));
        }
        if (goodSearch.getHearvyFrom() != null || goodSearch.getHearvyEnd() != null) {
            if (goodSearch.getHearvyFrom() != null && goodSearch.getHearvyEnd() != null) {
                query.addCriteria(hearvy.between(goodSearch.getHearvyFrom(), goodSearch.getHearvyEnd()));
            } else if (goodSearch.getHearvyFrom() != null) {
                query.addCriteria(hearvy.ge(goodSearch.getHearvyFrom()));
            } else if (goodSearch.getHearvyEnd() != null) {
                query.addCriteria(hearvy.le(goodSearch.getHearvyEnd()));
            }
            if (goodSearch.getGsHearvyUnin() != null) {
                query.addCriteria(hearvyUnin.eq(goodSearch.getGsHearvyUnin()));
            }
        }
        if (goodSearch.getGsFromMi() != null || goodSearch.getGsEndMi() != null) {
            if (goodSearch.getGsFromMi() != null && goodSearch.getGsEndMi() != null) {
                query.addCriteria(mi.between(goodSearch.getGsFromMi(), goodSearch.getGsEndMi()));
            } else if (goodSearch.getGsFromMi() != null) {
                query.addCriteria(mi.ge(goodSearch.getGsFromMi()));
            } else if (goodSearch.getGsEndMi() != null) {
                query.addCriteria(mi.le(goodSearch.getGsEndMi()));
            }
            if (goodSearch.getGsCarType() != null) {
                query.addCriteria(carType.eq(goodSearch.getGsCarType()));
            }
        }
        if (goodSearch.getLastIndex() != null) {
            query.addCriteria(Query.KEY.lt(goodSearch.getLastIndex()));
            query.includeValues().maxResults(10).addOrderBy(query.KEY, Direction.DESCENDING);
        } else {
            query.includeValues().maxResults(30).addOrderBy(query.KEY, Direction.DESCENDING);
        }
        return query.execute().all();
    }

    public void killGoodBeforeTime(Date timestamp) {
        for (Integer cityId : savePoint.keySet()) {
            try {
                if (cacheManager.getStatus() == Status.STATUS_SHUTDOWN) {
                    break;
                }
                Ehcache ehcache = cacheManager.getEhcache(String.valueOf(cityId));
                Attribute<Date> addTime = ehcache.getSearchAttribute(ADD_TIME_KEY);
                Query query = ehcache.createQuery().includeKeys().addCriteria(addTime.lt(timestamp));
                Results rss = query.execute();
                if (rss.size() == 0) {
                    continue;
                }
                for (Result result : rss.all()) {
                    ehcache.remove(result.getKey());
                }
                rss.discard();
            }
            catch (Exception e) {
                log.error(e.getMessage(), e);
            }

        }
    }

    public Ehcache getCityCache(Integer cityId) {
        return cacheManager.getEhcache(String.valueOf(cityId));
    }

}
